package net.findbugs.demo.detect;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import net.findbugs.demo.analysis.LockCount;
import net.findbugs.demo.analysis.LockCountDataflow;

import org.apache.bcel.Constants;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.FieldInstruction;

import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.Priorities;
import edu.umd.cs.findbugs.SourceLineAnnotation;
import edu.umd.cs.findbugs.ba.CFG;
import edu.umd.cs.findbugs.ba.DataflowAnalysisException;
import edu.umd.cs.findbugs.ba.Location;
import edu.umd.cs.findbugs.ba.XFactory;
import edu.umd.cs.findbugs.ba.XField;
import edu.umd.cs.findbugs.bcel.CFGDetector;
import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
import edu.umd.cs.findbugs.classfile.Global;
import edu.umd.cs.findbugs.classfile.IAnalysisCache;
import edu.umd.cs.findbugs.classfile.MethodDescriptor;

public class FindInconsistentSync extends CFGDetector {
	
	static class AccessCount {
		int numSyncAccesses;
		int numUnsyncAccesses;
		List<SourceLineAnnotation> unsyncLocList =
			new LinkedList<SourceLineAnnotation>();
		
		boolean isInconsistent() {
			return numSyncAccesses > 0 && numUnsyncAccesses > 0;
		}

		public void addSyncAccess() {
			numSyncAccesses++;
		}
		
		void addUnsyncAccess(SourceLineAnnotation sourceLoc) {
			numUnsyncAccesses++;
			unsyncLocList.add(sourceLoc);
		}
	}
	
	private BugReporter bugReporter;
	private Map<XField, AccessCount> accessMap;
	
	public FindInconsistentSync(BugReporter bugReporter) {
		this.bugReporter = bugReporter;
		this.accessMap = new HashMap<XField, AccessCount>();
	}

	private AccessCount getAccessCount(XField field) {
		AccessCount accessCount = accessMap.get(field);
		if (accessCount == null) {
			accessCount = new AccessCount();
			accessMap.put(field, accessCount);
		}
		return accessCount;
	}

	@Override
	protected void visitMethodCFG(MethodDescriptor methodDescriptor, CFG cfg)
			throws CheckedAnalysisException {
		IAnalysisCache analysisCache = Global.getAnalysisCache();
		
		ConstantPoolGen cpg = analysisCache.getClassAnalysis(
				ConstantPoolGen.class, methodDescriptor.getClassDescriptor());
		LockCountDataflow lockCountDataflow = analysisCache.getMethodAnalysis(
				LockCountDataflow.class, methodDescriptor);
		
		for (Iterator<Location> i = cfg.locationIterator(); i.hasNext();) {
			Location location = i.next();
			short opcode = location.getHandle().getInstruction().getOpcode();
			if (opcode != Constants.GETFIELD && opcode != Constants.PUTFIELD) {
				continue;
			}

			inspectFieldAccess(methodDescriptor, cpg, lockCountDataflow, location);
		}
	}

	private void inspectFieldAccess(MethodDescriptor methodDescriptor, ConstantPoolGen cpg, LockCountDataflow lockCountDataflow, Location location) throws DataflowAnalysisException {
		LockCount lockCount = lockCountDataflow.getFactAtLocation(location);
		if (lockCount.getCount() < 0) {
			// Dead code?
			return;
		}
		
		XField field = XFactory.createXField(
				(FieldInstruction) location.getHandle().getInstruction(), cpg);
		AccessCount accessCount = getAccessCount(field);
		
		if (lockCount.getCount() > 0) {
			accessCount.addSyncAccess();
		} else {
			accessCount.numUnsyncAccesses++;
			accessCount.addUnsyncAccess(
				SourceLineAnnotation.fromVisitedInstruction(methodDescriptor, location));
		}
	}
	
	@Override
	public void finishPass() {
		Iterator<Map.Entry<XField, AccessCount>> i = accessMap.entrySet().iterator();
		while (i.hasNext()) {
			Map.Entry<XField, AccessCount> entry = i.next();
			
			if (!entry.getValue().isInconsistent()) {
				continue;
			}
			
			BugInstance warning =
				new BugInstance(this, "ISYNC_INCONSISTENT_SYNC", Priorities.NORMAL_PRIORITY);
			warning.addClass(entry.getKey().getClassName());
			warning.addField(entry.getKey());
			for (SourceLineAnnotation sourceLoc : entry.getValue().unsyncLocList) {
				warning.addSourceLine(sourceLoc).describe("SOURCE_LINE_UNSYNC_ACCESS");
			}
			
			bugReporter.reportBug(warning);
		}
	}

}
